#include <iostream>
#include <string>
#include <unordered_map>
#include <sys/select.h>

#include "./sock.hpp"

#define NUM (sizeof(fd_set) * 8)

//提前预设好的英语和中文之间的关系
std::unordered_map<std::string, std::string> dict = {{"hello", "你好"}};

//这个数组用来保存用户提醒内核,哪些fd是你需要进行关注的
//如果当前位置的值为-1,那么说明这个位置没有fd
//其他的>=0的值,都视作fd的数值
int fd_array[NUM];

void usage(std::string proc)
{
    std::cout << "usage: " << proc << " + port" << std::endl;
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(0);
    }

    //创建监听套接字
    int listen_sock = Sock::Socket();

    //绑定
    uint16_t port = static_cast<uint16_t>(atoi(argv[1]));
    Sock::Bind(listen_sock, port);

    //监听
    Sock::Listen(listen_sock, 5);

    //这里不应该使用accept,accept的本质是通过listen_sock获取到新链接的
    //但是前提是listen_sock上面有新链接
    //那么accept是如何知道有新链接的呢?
    //accept阻塞式的等待就可以得知
    //那么我们这里可以把accept等待的过程,也使用多路转接
    //站在多路转接的视角,我们认为,链接的到来,对于listen_sock来说就是读事件就绪
    //我们的服务器,最开始的时候,只有listen_sock

    //fd_array进行初始化
    for (int i = 0; i < (int)NUM; ++i)
    {
        fd_array[i] = -1;
    }

    fd_array[0] = listen_sock;
    //需要读就绪条件的文件描述符的集合
    fd_set read_fds;

    //根据fd_array,来进行设置read_fds
    while(true)
    {
        FD_ZERO(&read_fds);

        int max_fd = fd_array[0];

        for (int i = 0; i < (int)NUM; ++i)
        {
            if (fd_array[i] == -1)
            {
                continue;
            }
            //以下为有效的fd
            FD_SET(fd_array[i], &read_fds); //将所有需要关注的fd加入到read_fds中
            //更新最大的fd
            if (max_fd < fd_array[i])
            {
                max_fd = fd_array[i];
            }

        }
           struct timeval timeout = {5, 0}; //5s
           //服务器上所有的fd(包括listen_sock),都需要交给select进行检测
           //recv,read,write,send,accept:只负责自己最核心的工作,读和写
           //对于listen_sock来说就是accpet
           int n = select(max_fd + 1, &read_fds, nullptr, nullptr, nullptr); //我们这里进行的是阻塞式
           switch(n)
           {
               case -1:
                   std::cerr << "select error" << std::endl;
                   break;
               case 0:
                   std::cout << "select timeout" << std::endl;
                   break;
               default:
                   std::cout << "有fd对应的事件就绪了!!!" << std::endl;
                    
                    //用轮询的方式查找,哪些的fd对应的事件就绪了
                    for (int i = 0; i < (int)NUM; ++i)
                    {
                        if (fd_array[i] == -1)
                        {
                            continue;
                        }

                        //下面的fd都是合法的fd,但是合法的fd不一定就是就绪的fd
                        if (FD_ISSET(fd_array[i], &read_fds))
                        {
                            //到这里就说明一定是读事件就绪了
                            //就绪的fd就在fd_array[i]里面保存
                            //read,recv,accpet时,就一定不会被阻塞
                            if (fd_array[i] == listen_sock)
                            {
                                std::cout << "listen_sock: " << fd_array << " 有新链接已经到来了" << std::endl;
                                //新链接到来了,我们就可以使用accpet获取新链接了,这个时候不会进行阻塞
                                int sock = Sock::Accept(listen_sock);
                                if (sock >= 0)
                                {
                                    std::cout << "listen_sock: " << fd_array << " 获取新链接成功" << std::endl;
                                    //这里只是新链接来了,但是不会意味着数据也到来了,只有数据到来的时候,我们再使用recv和read,那么数据什么时候到来呢?
                                    //我们不知道,但是select可以通过新获取的fd来知道什么时候数据到来了,所以我们这里需要把sock设置进入fd_array中
                                    int pos = 1;
                                    for (; pos < (int)NUM; ++pos)
                                    {
                                        if (fd_array[pos] == -1)
                                        {
                                            //这个位置没有被使用,我们可以进行使用
                                            break;
                                        }
                                    }

                                    //1.找到了一个没有被使用的位置
                                    if (pos < (int)NUM)
                                    {
                                        fd_array[pos] = sock;
                                        std::cout << "新链接: " << sock << " 已经被添加到数据[" << pos << "]的位置" << std::endl;
                                    }
                                    //2.找完了所有的fd_array[],都没有找到没有被使用的位置,说明服务已经满载了,没有办法处理新的请求了
                                    else
                                    {
                                        close(sock);
                                        std::cout << "服务器已经满载,关闭了新的链接" << std::endl;
                                    }
                                }

                            }
                            //普通的fd读事件就绪了
                            //可以进行read,recv读取了
                            //但是想我们这种方式一次不一定能读完,就算一次性读完了,也有可能存在一定的粘包问题
                            //我们现在没有办法进行解决,因为我们现在没有使用场景,我们这里写的代码仅仅是用来进行测试的
                            else
                            {
                                std::cout << "sock: " << fd_array[i] << " 开始进行数据的读取" << std::endl;
                                char recv_buffer[1024] = {0};
                                ssize_t ret = recv(fd_array[i], recv_buffer, sizeof(recv_buffer) - 1, 0);
                                if (ret > 0)
                                {
                                    recv_buffer[ret] = '\0';
                                    std::cout << "client[" << fd_array[i] << "]# " << recv_buffer << std::endl;
                                    
                                    if (dict.find("hello") != dict.end())
                                    {
                                        strcpy(recv_buffer, "你好");
                                    }

                                    send(fd_array[i], recv_buffer, sizeof(recv_buffer), 0);
                                }
                                else if (ret == 0)
                                {
                                    //对端关闭了链接
                                    std::cout << "sock: " << fd_array[i] << "关闭了,client退出了!" << std::endl;
                                    close(fd_array[i]);
                                    std::cout << "已经在数组下标fd_array[" << i << "]中去掉了sock: " << fd_array[i] << std::endl; 
                                    fd_array[i] = -1;
                                }
                                else
                                {
                                    //读取失败
                                    close(fd_array[i]);
                                    std::cout << "读取失败, 已经在数组下标fd_array[" << i << "]中去掉了sock: " << fd_array[i] << std::endl; 
                                    fd_array[i] = -1;
                                }
                            }
                        }
                    }
                   break;
           }
    }
    close(listen_sock);
    return 0;
}

